package cn.chenfeifx.core.controls;

import cn.chenfeifx.core.common.AnimationUtils;
import cn.chenfeifx.core.common.FxUtil;
import cn.chenfeifx.core.common.ResourcesLoader;
import cn.chenfeifx.core.common.SimpleButton;
import cn.chenfeifx.core.enums.Level;
import javafx.animation.Timeline;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.WritableValue;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.*;
import javafx.util.Callback;
import javafx.util.Duration;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class CFDialog {

    private static final String STYLE_SHEET = ResourcesLoader.load("/cn/chenfeifx/core/css/cf-dialog.css");
    private Window owner;
    private final Stage stage = new Stage();
    private final Header header;
    protected final BorderPane container;

    public CFDialog(Node content, CFButtonType... buttonTypes) {
        container = new BorderPane(content);
        header = new Header("Title", event -> setResultAndClose(CFButtonType.CLOSE));
        container.setTop(header);
        container.setBottom(new CFAlert.Footer(
                event -> setResultAndClose(CFButtonType.OK),
                event -> setResultAndClose(CFButtonType.CANCEL),
                buttonTypes));
        initialize();
    }

    private void initialize() {
        Scene scene = new Scene(container);
        scene.setFill(null);
        stage.setScene(scene);
        stage.initStyle(StageStyle.TRANSPARENT);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setOpacity(0);
        //
        container.getStylesheets().add(STYLE_SHEET);
        container.getStyleClass().addAll("cf-dialog");
        //
        stage.setOnShown(event -> {
            // 设置窗口位置
            double x = this.owner.getX() + this.owner.getWidth() / 2 - this.stage.getWidth() / 2;
            double y = this.owner.getY() + this.owner.getHeight() / 2 - this.stage.getHeight() / 2;
            // 超越边界重新计算
            if (x < 0) {
                x = 0;
            }
            if (y < 0) {
                y = 0;
            }
            Rectangle2D visualBounds = Screen.getPrimary().getVisualBounds();
            if (x > visualBounds.getMaxX() - this.stage.getWidth()) {
                x = visualBounds.getMaxX() - this.stage.getWidth();
            }
            if (y > visualBounds.getMaxY() - this.stage.getHeight()) {
                y = visualBounds.getMaxY() - this.stage.getHeight();
            }
            this.stage.setX(x);
            this.stage.setY(y);
            // 显示并启动动画
            showAndAnimation();
        });
        stageMove();
    }

    public void setLevel(Level level) {
        level.resetStyleClass(container);
    }

    public void setContent(Node node) {
        container.setCenter(node);
    }

    public void setTitle(String title) {
        header.titleProperty().set(title);
    }

    public void show() {
        show((Window) null);
    }

    public void show(Node ownerNode) {
        show(FxUtil.getWindow(ownerNode));
    }

    public void show(Window owner) {
        if (owner == null) {
            owner = FxUtil.getFocusedWindow();
        }
        this.owner = owner;
        if (stage.getOwner() != this.owner) { // 新的所有者
            stage.initOwner(owner);
        }
        setOwnerDisable(true);
        stage.show();
    }

    // CFButtonType Callback
    private final ObjectProperty<Callback<CFButtonType, Boolean>> resultConverter
            = new SimpleObjectProperty<>(this, "resultConverter");

    public ObjectProperty<Callback<CFButtonType, Boolean>> resultConverter() {
        return resultConverter;
    }

    public Callback<CFButtonType, Boolean> getResultConverter() {
        return resultConverter().get();
    }

    public void setResultConverter(Callback<CFButtonType, Boolean> value) {
        this.resultConverter().set(value);
    }

    /**
     * 设置返回值并且关闭Alert
     *
     * @param cfButtonType
     */
    private void setResultAndClose(CFButtonType cfButtonType) {
        Callback<CFButtonType, Boolean> resultConverter = getResultConverter();
        if (resultConverter == null) {
            hideAndAnimation();
            return;
        }
        Boolean result = Optional.ofNullable(resultConverter.call(cfButtonType)).orElse(false);
        if (result) {
            hideAndAnimation();
        }
    }

    private void setOwnerDisable(boolean val) {
        stage.getOwner().getScene().getRoot().setDisable(val);
    }

    // 动画相关
    private final Button button = new Button();

    private Timeline showAnimation;
    private Timeline hideAnimation;

    /**
     * 显示动画
     */
    private void showAndAnimation() {
        if (showAnimation == null) {
            button.layoutYProperty().addListener((observable, oldValue, newValue) -> stage.setY(newValue.doubleValue()));
            showAnimation = AnimationUtils.parallel(Duration.millis(250),
                    new WritableValue[]{stage.opacityProperty(), button.layoutYProperty()},
                    new Object[]{0, stage.getY() - 20},
                    new Object[]{1, stage.getY()},
                    event -> {
                    });
        }
        showAnimation.play();
    }

    /**
     * 隐藏动画
     */
    private void hideAndAnimation() {
        if (hideAnimation == null) {
            hideAnimation = AnimationUtils.parallel(Duration.millis(250),
                    new WritableValue[]{stage.opacityProperty(), button.layoutYProperty()},
                    new Object[]{1, stage.getY()},
                    new Object[]{0, stage.getY() - 20},
                    event -> {
                        this.stage.close();
                        setOwnerDisable(false);
                    });
        }
        hideAnimation.play();
    }

    // 窗口拖动
    private double xOffset;
    private double yOffset;

    private void stageMove() {
        this.container.setOnMousePressed(event -> {
            event.consume();
            this.xOffset = this.stage.getX() - event.getScreenX();
            this.yOffset = this.stage.getY() - event.getScreenY();
        });
        this.container.setOnMouseDragged(event -> {
            event.consume();
            this.stage.setX(event.getScreenX() + this.xOffset);
            this.stage.setY(event.getScreenY() + this.yOffset);
        });
    }

    /**
     * 底部
     */
    public static class Footer extends HBox {

        public Footer(EventHandler<MouseEvent> okEventEventHandler,
                      EventHandler<MouseEvent> cancelEventEventHandler,
                      CFButtonType... buttonTypes) {
            Button cancel = SimpleButton.get("Cancel", Level.PRIMARY).plain();
            Button ok = SimpleButton.get("Ok", Level.PRIMARY);
            //
            List<CFButtonType> bts = Arrays.asList(buttonTypes);
            if (bts.contains(CFButtonType.CANCEL)) {
                getChildren().add(cancel);
            }
            if (bts.contains(CFButtonType.OK)) {
                getChildren().add(ok);
            }
            setAlignment(Pos.CENTER_RIGHT);
            setSpacing(10);
            ok.setOnMouseClicked(okEventEventHandler);
            cancel.setOnMouseClicked(cancelEventEventHandler);
        }
    }

}
